package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.search.SearchAttr;
import com.google.common.collect.Lists;
import com.atguigu.gmall.search.vo.SearchResultVo.OrderMap;

import com.atguigu.gmall.search.Goods;
import com.atguigu.gmall.search.repository.GoodsRepository;
import com.atguigu.gmall.search.service.GoodsService;
import com.atguigu.gmall.search.vo.SearchParamVo;
import com.atguigu.gmall.search.vo.SearchResultVo;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author lfy
 * @Description
 * @create 2023-08-26 8:44
 */
@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    GoodsRepository goodsRepository;


    @Autowired
    ElasticsearchRestTemplate searchTemplate;

    @Override
    public void save(Goods goods) {
        goodsRepository.save(goods);
    }

    @Override
    public void deleteById(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    @Override
    public SearchResultVo search(SearchParamVo searchParam) {


        //1、准备一个Query对象。DSL： 根据前端传来的所有条件，构建一个复杂的DSL语句【就是Query对象】，
        Query query = buildQuery(searchParam);

        //2、搜索
        SearchHits<Goods> goods = searchTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));

        //TODO 3、根据搜索得到的结果，将数据封装成页面能用的vo
        SearchResultVo result = buildResult(goods, searchParam);

        return result;
    }


    //增加分： 1：拿到原分（查询）  2、计算新分   3、保存新分（保存）
    //更新分： 远程把新分传给我们， 1、修改新分（update）
    @Override
    public void updateHotScore(Long skuId, Long score) {

        Document document = Document.create();
        document.put("hotScore",score);


        //1、增量更新、局部更新
        UpdateQuery query = UpdateQuery
                .builder(skuId.toString())
                .withDocAsUpsert(true)
                .withDocument(document)
                .build();
        //部分字段更新
        searchTemplate.update(query,IndexCoordinates.of("goods"));
    }


    /**
     * 根据检索结果构建 页面数据
     *
     * @param searchResult
     * @param searchParam
     * @return
     */
    private SearchResultVo buildResult(SearchHits<Goods> searchResult, SearchParamVo searchParam) {
        SearchResultVo result = new SearchResultVo();
        //1、检索参数
        result.setSearchParam(searchParam);


        //2、面包屑
        //2.1、品牌面包屑  trademark=4:小米
        if (StringUtils.hasText(searchParam.getTrademark())){
            result.setTrademarkParam("品牌："+searchParam.getTrademark().split(":")[1]);
        }

        //2.2、属性面包屑  1:2800-4499:价格
        //list.html?&category3Id=61&pageNo=1&props=1:2800-4499:价格&props=2:%206.95英寸及以上:屏幕尺寸&props=4:256GB:机身存储&props=5:麒麟990:CPU型号&order=1:asc
        List<SearchAttr> propsParamList = new ArrayList<>();
        if (searchParam.getProps()!=null && searchParam.getProps().length>0) {
            for (String prop : searchParam.getProps()) {
                String[] split = prop.split(":");
                SearchAttr attr = new SearchAttr();
                attr.setAttrId(Long.parseLong(split[0]));
                attr.setAttrValue(split[1]);
                attr.setAttrName(split[2]);

                propsParamList.add(attr);
            }
        }
        result.setPropsParamList(propsParamList);




        //拿到所有聚合结果的map
        Map<String, Aggregation> aggregationMap = searchResult.getAggregations().getAsMap();

        //3、品牌列表

        // ParsedLongTerms //Parsed+字段类型+聚合类型
        List<SearchResultVo.Trademark> trademarkList = new ArrayList<>();

        //得到品牌id的聚合桶
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //遍历桶里面的每一个元素得到每一个品牌
        for (Terms.Bucket bucket : tmIdAgg.getBuckets()) {
            //1）、获取到品牌id
            long tmId = bucket.getKeyAsNumber().longValue();

            //2）、获取品牌名：ParsedStringTerms
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();

            //3）、获取品牌logo：
            ParsedStringTerms tmLogoAgg = bucket.getAggregations().get("tmLogoAgg");
            String tmLogo = tmLogoAgg.getBuckets().get(0).getKeyAsString();

            SearchResultVo.Trademark trademark = new SearchResultVo.Trademark();
            trademark.setTmId(tmId);
            trademark.setTmName(tmName);
            trademark.setTmLogoUrl(tmLogo);

            trademarkList.add(trademark);
        }

        //3、品牌列表
        result.setTrademarkList(trademarkList);


        //4、属性列表
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");

        //拿到属性id聚合结果
        List<SearchResultVo.Attr> attrList = new ArrayList<>();
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResultVo.Attr attr = new SearchResultVo.Attr();

            //属性id
            long attrId = bucket.getKeyAsNumber().longValue();
            attr.setAttrId(attrId);

            //属性名
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();

            attr.setAttrName(attrName);

            //属性值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            List<String> values = attrValueAgg.getBuckets()
                    .stream()
                    .map(item -> item.getKeyAsString())
                    .collect(Collectors.toList());
            attr.setAttrValueList(values);


            attrList.add(attr);
        }

        result.setAttrsList(attrList);

        //5、商品列表
        List<Goods> goods = searchResult.getSearchHits()
                .stream()
                .map(item -> {
                    Goods content = item.getContent();

                    //设置 高亮新标题
                    if (StringUtils.hasText(searchParam.getKeyword())) {
                        String title = item.getHighlightField("title").get(0);
                        content.setTitle(title);
                    }

                    return content;
                })
                .collect(Collectors.toList());
        result.setGoodsList(goods);


        //以下都是为了回显

        //6、排序规则
        if(StringUtils.hasText(searchParam.getOrder())){
            String[] order = searchParam.getOrder().split(":");
            OrderMap orderMap = new OrderMap();
            orderMap.setType(order[0]);
            orderMap.setSort(order[1]);
            result.setOrderMap(orderMap);
        }


        //7、页码
        result.setPageNo(searchParam.getPageNo());
        //8、总页码
        long totalHits = searchResult.getTotalHits();
        long totalPages = totalHits%searchParam.getPageSize() == 0?
                totalHits/searchParam.getPageSize(): totalHits/searchParam.getPageSize() +1;
        result.setTotalPages(totalPages);

        //9、前端要求把当前访问路径要给他直接返回
        //两种办法：
        // 1、requestApi能直接获取到路径，获取到的东西需要自己再拼接
        // 2、完全自己造

        String urlParam = buildUrlParam(searchParam);
        result.setUrlParam(urlParam);

        return result;
    }


    /**
     * 前端要求返回: list.html?category3Id=3&props=
     * @param searchParam
     * @return
     */
    private String buildUrlParam(SearchParamVo searchParam) {
        //方法的局部变量是没有线程安全问题； 只有类的成员变量，由于单实例，导致多线程共享操作，就会有安全问题
        StringBuilder builder = new StringBuilder("list.html?");


        //分类
        if (searchParam.getCategory1Id()!=null) {
            builder.append("&category1Id="+searchParam.getCategory1Id());
        }

        if (searchParam.getCategory2Id()!=null) {
            builder.append("&category2Id="+searchParam.getCategory2Id());
        }

        if (searchParam.getCategory3Id()!=null) {
            builder.append("&category3Id="+searchParam.getCategory3Id());
        }

        //关键字
        if (StringUtils.hasText(searchParam.getKeyword())) {
            builder.append("&keyword="+searchParam.getKeyword());
        }


        //页码
//        if (searchParam.getPageNo()!=null) {
//            builder.append("&pageNo="+searchParam.getPageNo());
//        }

        //品牌
        if (StringUtils.hasText(searchParam.getTrademark())) {
            builder.append("&trademark="+searchParam.getTrademark());
        }


        //属性
        if (searchParam.getProps()!=null && searchParam.getProps().length>0) {
            for (String prop : searchParam.getProps()) {
                builder.append("&props="+prop);
            }
        }

        //排序
//        if (StringUtils.hasText(searchParam.getOrder())){
//            builder.append("&order="+searchParam.getOrder());
//        }




        return builder.toString();
    }


    /**
     * 根据前端参数构建query对象
     *
     * @param searchParam
     * @return
     */
    private Query buildQuery(SearchParamVo searchParam) {

        //native：本地；原生;
        //Cloud Native：云原生； 如何在云上部署原生应用

        //所有查询条件由 QueryBuilders 进行构建
        BoolQueryBuilder bool = QueryBuilders.boolQuery();


        //====构建分类查询条件======
        if (searchParam.getCategory1Id() != null) {
            bool.must(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }
        if (searchParam.getCategory2Id() != null) {
            bool.must(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        if (searchParam.getCategory3Id() != null) {
            bool.must(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }

        //====构建全文检索条件======
        if (StringUtils.hasText(searchParam.getKeyword())) {
            bool.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()));
        }


        //====构建品牌查询条件=====
        if (StringUtils.hasText(searchParam.getTrademark())) {
            bool.must(QueryBuilders.termQuery("tmId", searchParam.getTrademark().split(":")[0]));
        }

        //====构建平台属性检索条件=====
        if (searchParam.getProps() != null && searchParam.getProps().length > 0) {
            for (String prop : searchParam.getProps()) {
                String[] split = prop.split(":");
                //每个属性都是一个nested查询
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

                //按照属性id查询
                boolQuery.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                //按照属性值查询
                boolQuery.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));

                //构建nested
                NestedQueryBuilder nested = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);
                //放到最大的bool
                bool.must(nested);
            }
        }


        //1、构建DSL
        NativeSearchQuery dsl = new NativeSearchQuery(bool);


        //====构建排序条件====
        Sort sort = null;
        if (StringUtils.hasText(searchParam.getOrder())) {
            //1:asc : 1代表综合排序，就是按照热度分
            String[] split = searchParam.getOrder().split(":");
            switch (split[0]) {
                case "1":
                    sort = Sort.by("hotScore");
                    break;
                case "2":
                    sort = Sort.by("price");
                    break;
            }
            sort = "asc".equals(split[1]) ? sort.ascending() : sort.descending();
            dsl.addSort(sort);
        }

        //====构建分页条件====
        Integer pageNo = searchParam.getPageNo();
        //排序和分页参数封装到一起 ； spring家的页码从0开始
        Pageable pageable = PageRequest.of(pageNo - 1, searchParam.getPageSize());

        dsl.setPageable(pageable);


        //===================聚合分析条件=======================
        //====分析品牌分布===
        //1、分析品牌id分布
        TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmIdAgg")
                .field("tmId")
                .size(100);

        //2、分析品牌名
        TermsAggregationBuilder tmNameAgg = AggregationBuilders.terms("tmNameAgg")
                .field("tmName")
                .size(1);
        tmIdAgg.subAggregation(tmNameAgg);

        //3、分析品牌Logo
        TermsAggregationBuilder tmLogoAgg = AggregationBuilders.terms("tmLogoAgg")
                .field("tmLogoUrl")
                .size(1);
        tmIdAgg.subAggregation(tmLogoAgg);



        //===分析平台属性分布=====
        //1）、nested聚合
        NestedAggregationBuilder nestedAttr = AggregationBuilders.nested("attrAgg", "attrs");

        //按照attrid聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg")
                .field("attrs.attrId")
                .size(200);

        //按照 attrName 聚合
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attrNameAgg")
                .field("attrs.attrName")
                .size(1);

        attrIdAgg.subAggregation(attrNameAgg);

        //按照 attrValue 聚合
        TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attrValueAgg")
                .field("attrs.attrValue")
                .size(100);
        attrIdAgg.subAggregation(attrValueAgg);

        nestedAttr.subAggregation(attrIdAgg);



        //5、attr聚合生效
        dsl.addAggregation(nestedAttr);

        //4、tmId聚合生效
        dsl.addAggregation(tmIdAgg);

        //======高亮============
        if (StringUtils.hasText(searchParam.getKeyword())) {

            //构建高亮
            HighlightBuilder builder = new HighlightBuilder();

            builder = builder.field("title")
                    .preTags("<span style='color:red'>")
                    .postTags("</span>");


            HighlightQuery highlightQuery = new HighlightQuery(builder);
            dsl.setHighlightQuery(highlightQuery);
        }


        return dsl;
    }
}
